package org.robobinding.attribute;

import java.util.Collection;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.robobinding.PendingAttributesForView;
import org.robobinding.PendingAttributesForViewImpl;
import org.robobinding.PredefinedPendingAttributesForView;

import android.content.Context;
import android.view.View;

import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * 
 * @since 1.0
 * @version $Revision: 1.0 $
 * @author Robert Taylor
 */
public class PredefinedMappingsAttribute extends AbstractAttribute {
	private static final String MAPPING_PATTERN = "(\\w+)\\.(\\w+):($?\\{\\w+\\})";
	private static final Pattern MAPPING_COMPILED_PATTERN = Pattern.compile(MAPPING_PATTERN);
	private static final Pattern MAPPING_ATTRIBUTE_COMPILED_PATTERN = Pattern.compile("^\\[" + MAPPING_PATTERN + "(?:," + MAPPING_PATTERN + ")*\\]$");
	private final String attributeValue;

	public PredefinedMappingsAttribute(String name, String value) {
		super(name);
		this.attributeValue = value;

		if (!MAPPING_ATTRIBUTE_COMPILED_PATTERN.matcher(value).matches())
			throw new MalformedAttributeException(getName(), "Mapping attribute value: " + value + " contains invalid syntax.");
	}

	public Collection<PredefinedPendingAttributesForView> getViewMappings(Context context) {
		return new ItemMappingParser().parse(getName(), attributeValue, context).getPredefinedPendingAttributesForViewGroup();
	}

	private static class ItemMappingParser {
		public ViewMappings parse(String name, String value, Context context) {
			Matcher matcher = MAPPING_COMPILED_PATTERN.matcher(value);
			ViewMappings viewMappings = new ViewMappings();

			while (matcher.find()) {
				String viewIdString = matcher.group(1);
				String nestedAttributeName = matcher.group(2);
				String nestedAttributeValue = matcher.group(3);

				int viewId = context.getResources().getIdentifier(viewIdString, "id", "android");

				if (viewId == 0)
					throw new MalformedAttributeException(name, "View with id name: " + viewIdString + " in package: android could not be found");

				viewMappings.add(viewId, nestedAttributeName, nestedAttributeValue);
			}

			return viewMappings;
		}
	}

	private static class ViewMappings {
		private Map<Integer, ViewMapping> viewMappingsMap = Maps.newHashMap();

		void add(int viewId, String attributeName, String attributeValue) {
			ViewMapping existingViewMapping = viewMappingsMap.get(viewId);

			if (existingViewMapping != null) {
				existingViewMapping.add(attributeName, attributeValue);
			} else {
				viewMappingsMap.put(viewId, new ViewMapping(viewId, attributeName, attributeValue));
			}
		}

		public Collection<PredefinedPendingAttributesForView> getPredefinedPendingAttributesForViewGroup() {
			return Lists.<PredefinedPendingAttributesForView> newArrayList(viewMappingsMap.values());
		}
	}

	static class ViewMapping implements PredefinedPendingAttributesForView {
		int viewId;
		Map<String, String> bindingAttributes = Maps.newHashMap();

		public ViewMapping(int viewId, String attributeName, String attributeValue) {
			this.viewId = viewId;
			this.add(attributeName, attributeValue);
		}

		public void add(String attributeName, String attributeValue) {
			this.bindingAttributes.put(attributeName, attributeValue);
		}

		@Override
		public PendingAttributesForView createPendingAttributesForView(View rootView) {
			View childView = rootView.findViewById(viewId);
			return new PendingAttributesForViewImpl(childView, bindingAttributes);
		}

		@Override
		public boolean equals(Object other) {
			if (this == other)
				return true;
			if (!(other instanceof ViewMapping))
				return false;

			final ViewMapping that = (ViewMapping) other;
			return Objects.equal(bindingAttributes, that.bindingAttributes) && Objects.equal(viewId, that.viewId);
		}

		@Override
		public int hashCode() {
			return Objects.hashCode(bindingAttributes, viewId);
		}
	}

	public static PredefinedMappingsAttribute nullAttribute(String attributeName) {
		return new PredefinedMappingsAttribute(attributeName, "[text1.text:{property}]") {
			@Override
			public Collection<PredefinedPendingAttributesForView> getViewMappings(Context context) {
				return Lists.newArrayList();
			}
		};
	}
}
